using System;
using System.Collections.Generic;
using System.Text;
using Krilib;
using Krilib.Bridges;
using Krilib.Plugin;
using Krilib.Plugin.Operations;
using Krilib.Data;
using System.Xml.Serialization;
using System.IO;
using System.Reflection;

namespace Krile.Cores
{
    /// <summary>
    /// Plugin Manager
    /// </summary>
    internal sealed class PluginManager : IPluginManager
    {
        //Plugin Authorize Manager
        private PManElems.PluginAuth pauth;
        //Authorized Plugins
        private Dictionary<string, PluginData> authorizedPlugins;
        //Unauthorized plugins
        private List<PluginData> unAuthorizedPlugins;

        /// <summary>
        /// Plugin authorize helper
        /// </summary>
        internal PManElems.PluginAuth PluginAuth
        {
            get { return pauth; }
        }

        /// <summary>
        /// Pluginmanager constructor
        /// </summary>
        internal PluginManager()
        {
            authorizedPlugins = new Dictionary<string, PluginData>();
            unAuthorizedPlugins = new List<PluginData>();
            pauth = new Krile.Cores.PManElems.PluginAuth();
        }

        /// <summary>
        /// Load plugins
        /// </summary>
        internal void LoadPlugins()
        {
            this.PluginAuth.SerializeEncryptedKeys = Core.Config.Security.PluginAuthSerializeKeyBuffer;
            PluginData[] pds = PluginData.GetPlugins(Path.Combine(Define.AppPath, Define.PluginDir));
            if (pds == null) return;
            foreach (PluginData pd in pds)
            {
                //Check authorize state of this plugin
                if (pauth.ConfirmAuthorize(pd))
                {
                    //Authorized
                    try
                    {
                        pd.CreateInstance();
                        authorizedPlugins.Add(pd.Instance.Id, pd);
                    }
                    catch (Exception e)
                    {
                        Subsystem.Debugger.AddReport(new DebugReport(e, DebugReport.Priorities.Warn));
                    }
                }
                else
                {
                    unAuthorizedPlugins.Add(pd);
                }
            }
        }

        /// <summary>
        /// Get all plugin array
        /// </summary>
        /// <returns>plugin array</returns>
        internal PluginData[] GetAllPluginDatas()
        {
            List<PluginData> ret = new List<PluginData>();
            ret.AddRange(authorizedPlugins.Values);
            ret.AddRange(unAuthorizedPlugins);
            return ret.ToArray();
        }

        /// <summary>
        /// Lookup plugin
        /// </summary>
        /// <param name="id">plugin id</param>
        /// <returns>instance or null</returns>
        public IPlugin Lookup(string id)
        {
            if (authorizedPlugins.ContainsKey(id))
            {
                return authorizedPlugins[id].Instance;
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Lookup plugin data
        /// </summary>
        /// <param name="id">plugin id</param>
        /// <returns>plugindata or null</returns>
        internal PluginData LookupPluginData(string id)
        {
            if (authorizedPlugins.ContainsKey(id))
            {
                return authorizedPlugins[id];
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Lookup service by plugin
        /// </summary>
        /// <param name="id">service id</param>
        /// <returns>servicer or null</returns>
        public Service LookupService(string id)
        {
            foreach (var plugin in authorizedPlugins.Values)
            {
                if (plugin.Instance != null &&
                    plugin.Instance.Operation is IAgent &&
                    ((IAgent)plugin.Instance.Operation).HostingService.Id == id)
                    return ((IAgent)plugin.Instance.Operation).HostingService;
            }
            return null;
        }

        internal IEnumerable<Service> GetServiceEnumerator()
        {
            foreach (var plugin in authorizedPlugins.Values)
            {
                if (plugin.Instance != null &&
                    plugin.Instance.Operation is IAgent)
                    yield return ((IAgent)plugin.Instance.Operation).HostingService;
            }
        }

        /// <summary>
        /// Get plugin configuration pages
        /// </summary>
        /// <returns></returns>
        internal Dictionary<string, Dictionary<string, Krilib.Control.ConfigPage>> GetConfigPages()
        {
            Dictionary<string, Dictionary<string, Krilib.Control.ConfigPage>> cfgPages = 
                new Dictionary<string, Dictionary<string, Krilib.Control.ConfigPage>>();
            foreach (PluginData pd in authorizedPlugins.Values)
            {
                cfgPages.Add(pd.Instance.Id, pd.Instance.Operation.GetConfigPages());
            }
            return cfgPages;
        }

        /// <summary>
        /// Start plugins
        /// </summary>
        internal void StartPlugin()
        {
            foreach (PluginData pd in authorizedPlugins.Values)
            {
                pd.Instance.Operation.Begin();
            }
        }

        /// <summary>
        /// End plugins
        /// </summary>
        internal void EndPlugin()
        {
            foreach (PluginData pd in authorizedPlugins.Values)
            {
                pd.Instance.Operation.Exit();
            }
        }
    }

    /// <summary>
    /// Plugin data class
    /// </summary>
    internal sealed class PluginData
    {
        string filePath;
        /// <summary>
        /// Plugin file path
        /// </summary>
        internal string FilePath
        {
            get { return filePath; }
        }

        string className;
        /// <summary>
        /// Plugin class name
        /// </summary>
        internal string ClassName
        {
            get { return className; }
        }

        string name;
        /// <summary>
        /// Plugin name
        /// </summary>
        internal string Name
        {
            get { return name; }
        }

        string description;
        /// <summary>
        /// Plugin description
        /// </summary>
        internal string Description
        {
            get { return description; }
        }

        /// <summary>
        /// Plugin instance
        /// </summary>
        internal IPlugin Instance = null;

        /// <summary>
        /// Plugindata private constructor
        /// </summary>
        /// <param name="fn">PluginPath</param>
        /// <param name="cls">ClassName</param>
        private PluginData(string fn,string cls)
        {
            filePath = fn;
            className = cls;
            name = null;
            description = null;
            ReloadDescription();
        }

        /// <summary>
        /// Reload this plugin's description.
        /// </summary>
        /// <remarks>
        /// <para>When this plugin's instance created,I'll pick-up it's description by itself.</para> 
        /// <para>Else,I'll pick up it's description by "PLUGINFILE.description" file.</para>
        /// </remarks>
        internal void ReloadDescription()
        {
            name = null;
            description = null;
            if (Instance != null)
            {
                name = Instance.Name;
                description = Instance.Description;
            }
            else
            {
                string descfile = filePath + ".description";
                if (!File.Exists(descfile)) return;
                PluginDescription pd = Krile.Imported.Snippets.Files.LoadXML<PluginDescription>(descfile, false);
                if (pd != null)
                {
                    name = pd.Name;
                    description = pd.Description;
                }
            }
        }

        /// <summary>
        /// Check this plugin have authorized
        /// </summary>
        internal bool Loadable
        {
            get
            {
                return Core.PluginManager.PluginAuth.GetAuthorize(this);
            }
        }

        /// <summary>
        /// Create this plugin's instance.
        /// </summary>
        /// <exception cref="ApplicationException">Plugin un-authorized</exception>
        /// <exception cref="Exception">General error</exception>
        internal void CreateInstance()
        {
            //Not Authorized
            if (!Loadable)
                throw new ApplicationException(Lang.Msg.Plugin_Not_Authorized);
            Assembly asm = Assembly.LoadFrom(filePath);
            this.Instance = (IPlugin)asm.CreateInstance(className);
            ReloadDescription();
        }

        /// <summary>
        /// Get all existed plugins list in target dir
        /// </summary>
        /// <param name="dir">Target Directory</param>
        /// <returns>PluginData Instances Array</returns>
        static internal PluginData[] GetPlugins(string dir)
        {
            List<PluginData> plugins = new List<PluginData>();
            string piName = typeof(IPlugin).FullName;
            if (!Directory.Exists(dir))
                //Plugin directory not found.
                return null;
            String[] targets = Directory.GetFiles(dir, "*.dll");
            foreach (String dll in targets)
            {
                try
                {
                    Assembly asm = Assembly.LoadFrom(dll);
                    foreach (Type type in asm.GetTypes())
                    {
                        //check class property
                        if (type.IsClass && type.IsPublic && !type.IsAbstract &&
                            type.GetInterface(piName) != null)
                            //plugin class verified,step to load!
                            plugins.Add(new PluginData(dll, type.FullName));
                    }
                }
                catch (Exception e)
                {
                    //Add debug log report
                    Subsystem.Debugger.AddReport(new DebugReport(e, DebugReport.Priorities.Warn));
                }
            }
            return plugins.ToArray();
        }
    }

    /// <summary>
    /// Plugin description class
    /// </summary>
    public class PluginDescription
    {
        private string name = null;
        /// <summary>
        /// Plugin Name
        /// </summary>
        public virtual string Name
        {
            get { return name; }
            set { name = value; }
        }

        private string description = null;
        /// <summary>
        /// Plugin Description
        /// </summary>
        public virtual string Description
        {
            get { return description; }
            set { description = value; }
        }
    }

}
